// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/input/input_router_impl.h"

#include <math.h>

#include <utility>

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/renderer_host/input/gesture_event_queue.h"
#include "content/browser/renderer_host/input/input_ack_handler.h"
#include "content/browser/renderer_host/input/input_router_client.h"
#include "content/browser/renderer_host/input/touch_event_queue.h"
#include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
#include "content/common/content_constants_internal.h"
#include "content/common/edit_command.h"
#include "content/common/input/input_event_ack_state.h"
#include "content/common/input/touch_action.h"
#include "content/common/input/web_touch_event_traits.h"
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "ipc/ipc_sender.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/blink/web_input_event_traits.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"

using base::Time;
using base::TimeDelta;
using base::TimeTicks;
using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebKeyboardEvent;
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebTouchEvent;
using ui::WebInputEventTraits;

namespace content {
namespace {

    const char* GetEventAckName(InputEventAckState ack_result)
    {
        switch (ack_result) {
        case INPUT_EVENT_ACK_STATE_UNKNOWN:
            return "UNKNOWN";
        case INPUT_EVENT_ACK_STATE_CONSUMED:
            return "CONSUMED";
        case INPUT_EVENT_ACK_STATE_NOT_CONSUMED:
            return "NOT_CONSUMED";
        case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS:
            return "NO_CONSUMER_EXISTS";
        case INPUT_EVENT_ACK_STATE_IGNORED:
            return "IGNORED";
        case INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING:
            return "SET_NON_BLOCKING";
        case INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING_DUE_TO_FLING:
            return "SET_NON_BLOCKING_DUE_TO_FLING";
        }
        DLOG(WARNING) << "Unhandled InputEventAckState in GetEventAckName.";
        return "";
    }

} // namespace

InputRouterImpl::Config::Config()
{
}

InputRouterImpl::InputRouterImpl(IPC::Sender* sender,
    InputRouterClient* client,
    InputAckHandler* ack_handler,
    int routing_id,
    const Config& config)
    : sender_(sender)
    , client_(client)
    , ack_handler_(ack_handler)
    , routing_id_(routing_id)
    , frame_tree_node_id_(-1)
    , select_message_pending_(false)
    , move_caret_pending_(false)
    , current_ack_source_(ACK_SOURCE_NONE)
    , flush_requested_(false)
    , active_renderer_fling_count_(0)
    , touch_scroll_started_sent_(false)
    , wheel_event_queue_(this,
          base::FeatureList::IsEnabled(
              features::kTouchpadAndWheelScrollLatching))
    , touch_event_queue_(this, config.touch_config)
    , gesture_event_queue_(this, this, config.gesture_config)
    , device_scale_factor_(1.f)
{
    DCHECK(sender);
    DCHECK(client);
    DCHECK(ack_handler);
    UpdateTouchAckTimeoutEnabled();
}

InputRouterImpl::~InputRouterImpl()
{
}

bool InputRouterImpl::SendInput(std::unique_ptr<IPC::Message> message)
{
    DCHECK(IPC_MESSAGE_ID_CLASS(message->type()) == InputMsgStart);
    switch (message->type()) {
    // Check for types that require an ACK.
    case InputMsg_SelectRange::ID:
    case InputMsg_MoveRangeSelectionExtent::ID:
        return SendSelectMessage(std::move(message));
    case InputMsg_MoveCaret::ID:
        return SendMoveCaret(std::move(message));
    case InputMsg_HandleInputEvent::ID:
        NOTREACHED() << "WebInputEvents should never be sent via SendInput.";
        return false;
    default:
        return Send(message.release());
    }
}

void InputRouterImpl::SendMouseEvent(
    const MouseEventWithLatencyInfo& mouse_event)
{
    if (mouse_event.event.type() == WebInputEvent::MouseDown && gesture_event_queue_.GetTouchpadTapSuppressionController()->ShouldDeferMouseDown(mouse_event))
        return;
    if (mouse_event.event.type() == WebInputEvent::MouseUp && gesture_event_queue_.GetTouchpadTapSuppressionController()->ShouldSuppressMouseUp())
        return;

    SendMouseEventImmediately(mouse_event);
}

void InputRouterImpl::SendWheelEvent(
    const MouseWheelEventWithLatencyInfo& wheel_event)
{
    wheel_event_queue_.QueueEvent(wheel_event);
}

void InputRouterImpl::SendKeyboardEvent(
    const NativeWebKeyboardEventWithLatencyInfo& key_event)
{
    // Put all WebKeyboardEvent objects in a queue since we can't trust the
    // renderer and we need to give something to the HandleKeyboardEvent
    // handler.
    key_queue_.push_back(key_event);
    LOCAL_HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size());

    gesture_event_queue_.FlingHasBeenHalted();

    // Only forward the non-native portions of our event.
    FilterAndSendWebInputEvent(key_event.event, key_event.latency);
}

void InputRouterImpl::SendGestureEvent(
    const GestureEventWithLatencyInfo& original_gesture_event)
{
    input_stream_validator_.Validate(original_gesture_event.event);

    GestureEventWithLatencyInfo gesture_event(original_gesture_event);

    if (touch_action_filter_.FilterGestureEvent(&gesture_event.event))
        return;

    wheel_event_queue_.OnGestureScrollEvent(gesture_event);

    if (gesture_event.event.sourceDevice == blink::WebGestureDeviceTouchscreen) {
        if (gesture_event.event.type() == blink::WebInputEvent::GestureScrollBegin) {
            touch_scroll_started_sent_ = false;
        } else if (!touch_scroll_started_sent_ && gesture_event.event.type() == blink::WebInputEvent::GestureScrollUpdate) {
            // A touch scroll hasn't really started until the first
            // GestureScrollUpdate event.  Eg. if the page consumes all touchmoves
            // then no scrolling really ever occurs (even though we still send
            // GestureScrollBegin).
            touch_event_queue_.PrependTouchScrollNotification();
            touch_scroll_started_sent_ = true;
        }
        touch_event_queue_.OnGestureScrollEvent(gesture_event);
    }

    gesture_event_queue_.QueueEvent(gesture_event);
}

void InputRouterImpl::SendTouchEvent(
    const TouchEventWithLatencyInfo& touch_event)
{
    input_stream_validator_.Validate(touch_event.event);
    touch_event_queue_.QueueEvent(touch_event);
}

// Forwards MouseEvent without passing it through
// TouchpadTapSuppressionController.
void InputRouterImpl::SendMouseEventImmediately(
    const MouseEventWithLatencyInfo& mouse_event)
{
    if (mouse_event.event.type() == blink::WebInputEvent::MouseMove)
        mouse_move_queue_.push_back(mouse_event);

    FilterAndSendWebInputEvent(mouse_event.event, mouse_event.latency);
}

void InputRouterImpl::SendTouchEventImmediately(
    const TouchEventWithLatencyInfo& touch_event)
{
    if (WebTouchEventTraits::IsTouchSequenceStart(touch_event.event)) {
        touch_action_filter_.ResetTouchAction();
        // Note that if the previous touch-action was TOUCH_ACTION_NONE, enabling
        // the timeout here will not take effect until the *following* touch
        // sequence.  This is a desirable side-effect, giving the renderer a chance
        // to send a touch-action response without racing against the ack timeout.
        UpdateTouchAckTimeoutEnabled();
    }

    FilterAndSendWebInputEvent(touch_event.event, touch_event.latency);
}

void InputRouterImpl::SendGestureEventImmediately(
    const GestureEventWithLatencyInfo& gesture_event)
{
    FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency);
}

const NativeWebKeyboardEvent* InputRouterImpl::GetLastKeyboardEvent() const
{
    if (key_queue_.empty())
        return NULL;
    return &key_queue_.front().event;
}

void InputRouterImpl::NotifySiteIsMobileOptimized(bool is_mobile_optimized)
{
    touch_event_queue_.SetIsMobileOptimizedSite(is_mobile_optimized);
}

void InputRouterImpl::RequestNotificationWhenFlushed()
{
    flush_requested_ = true;
    SignalFlushedIfNecessary();
}

bool InputRouterImpl::HasPendingEvents() const
{
    return !touch_event_queue_.empty() || !gesture_event_queue_.empty() || !key_queue_.empty() || !mouse_move_queue_.empty() || wheel_event_queue_.has_pending() || select_message_pending_ || move_caret_pending_ || active_renderer_fling_count_ > 0;
}

void InputRouterImpl::SetDeviceScaleFactor(float device_scale_factor)
{
    device_scale_factor_ = device_scale_factor;
}

bool InputRouterImpl::OnMessageReceived(const IPC::Message& message)
{
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(InputRouterImpl, message)
    IPC_MESSAGE_HANDLER(InputHostMsg_HandleInputEvent_ACK, OnInputEventAck)
    IPC_MESSAGE_HANDLER(InputHostMsg_DidOverscroll, OnDidOverscroll)
    IPC_MESSAGE_HANDLER(InputHostMsg_MoveCaret_ACK, OnMsgMoveCaretAck)
    IPC_MESSAGE_HANDLER(InputHostMsg_SelectRange_ACK, OnSelectMessageAck)
    IPC_MESSAGE_HANDLER(InputHostMsg_MoveRangeSelectionExtent_ACK,
        OnSelectMessageAck)
    IPC_MESSAGE_HANDLER(ViewHostMsg_HasTouchEventHandlers,
        OnHasTouchEventHandlers)
    IPC_MESSAGE_HANDLER(InputHostMsg_SetTouchAction,
        OnSetTouchAction)
    IPC_MESSAGE_HANDLER(InputHostMsg_DidStopFlinging, OnDidStopFlinging)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()

    return handled;
}

void InputRouterImpl::OnTouchEventAck(const TouchEventWithLatencyInfo& event,
    InputEventAckState ack_result)
{
    // Touchstart events sent to the renderer indicate a new touch sequence, but
    // in some cases we may filter out sending the touchstart - catch those here.
    if (WebTouchEventTraits::IsTouchSequenceStart(event.event) && ack_result == INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) {
        touch_action_filter_.ResetTouchAction();
        UpdateTouchAckTimeoutEnabled();
    }
    ack_handler_->OnTouchEventAck(event, ack_result);
}

void InputRouterImpl::OnFilteringTouchEvent(
    const WebTouchEvent& touch_event)
{
    // The event stream given to the renderer is not guaranteed to be
    // valid based on the current TouchEventStreamValidator rules. This event will
    // never be given to the renderer, but in order to ensure that the event
    // stream |output_stream_validator_| sees is valid, we give events which are
    // filtered out to the validator. crbug.com/589111 proposes adding an
    // additional validator for the events which are actually sent to the
    // renderer.
    output_stream_validator_.Validate(touch_event);
}

void InputRouterImpl::OnGestureEventAck(
    const GestureEventWithLatencyInfo& event,
    InputEventAckState ack_result)
{
    touch_event_queue_.OnGestureEventAck(event, ack_result);
    ack_handler_->OnGestureEventAck(event, ack_result);
}

void InputRouterImpl::ForwardGestureEventWithLatencyInfo(
    const blink::WebGestureEvent& event,
    const ui::LatencyInfo& latency_info)
{
    client_->ForwardGestureEventWithLatencyInfo(event, latency_info);
}

void InputRouterImpl::SendMouseWheelEventImmediately(
    const MouseWheelEventWithLatencyInfo& wheel_event)
{
    FilterAndSendWebInputEvent(wheel_event.event, wheel_event.latency);
}

void InputRouterImpl::OnMouseWheelEventAck(
    const MouseWheelEventWithLatencyInfo& event,
    InputEventAckState ack_result)
{
    ack_handler_->OnWheelEventAck(event, ack_result);
}

bool InputRouterImpl::SendSelectMessage(std::unique_ptr<IPC::Message> message)
{
    DCHECK(message->type() == InputMsg_SelectRange::ID || message->type() == InputMsg_MoveRangeSelectionExtent::ID);

    // TODO(jdduke): Factor out common logic between selection and caret-related
    //               IPC messages.
    if (select_message_pending_) {
        if (!pending_select_messages_.empty() && pending_select_messages_.back()->type() == message->type()) {
            pending_select_messages_.pop_back();
        }

        pending_select_messages_.push_back(std::move(message));
        return true;
    }

    select_message_pending_ = true;
    return Send(message.release());
}

bool InputRouterImpl::SendMoveCaret(std::unique_ptr<IPC::Message> message)
{
    DCHECK(message->type() == InputMsg_MoveCaret::ID);
    if (move_caret_pending_) {
        next_move_caret_ = std::move(message);
        return true;
    }

    move_caret_pending_ = true;
    return Send(message.release());
}

bool InputRouterImpl::Send(IPC::Message* message)
{
    return sender_->Send(message);
}

void InputRouterImpl::FilterAndSendWebInputEvent(
    const WebInputEvent& input_event,
    const ui::LatencyInfo& latency_info)
{
    TRACE_EVENT1("input", "InputRouterImpl::FilterAndSendWebInputEvent", "type",
        WebInputEvent::GetName(input_event.type()));
    TRACE_EVENT_WITH_FLOW2("input,benchmark,devtools.timeline",
        "LatencyInfo.Flow",
        TRACE_ID_DONT_MANGLE(latency_info.trace_id()),
        TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
        "step", "SendInputEventUI",
        "frameTreeNodeId", frame_tree_node_id_);

    OfferToHandlers(input_event, latency_info);
}

void InputRouterImpl::OfferToHandlers(const WebInputEvent& input_event,
    const ui::LatencyInfo& latency_info)
{
    output_stream_validator_.Validate(input_event);

    if (OfferToClient(input_event, latency_info))
        return;

    bool should_block = WebInputEventTraits::ShouldBlockEventStream(input_event);
    OfferToRenderer(input_event, latency_info,
        should_block
            ? InputEventDispatchType::DISPATCH_TYPE_BLOCKING
            : InputEventDispatchType::DISPATCH_TYPE_NON_BLOCKING);

    // Generate a synthetic ack if the event was sent so it doesn't block.
    if (!should_block) {
        ProcessInputEventAck(
            input_event.type(), INPUT_EVENT_ACK_STATE_IGNORED, latency_info,
            WebInputEventTraits::GetUniqueTouchEventId(input_event),
            IGNORING_DISPOSITION);
    }
}

bool InputRouterImpl::OfferToClient(const WebInputEvent& input_event,
    const ui::LatencyInfo& latency_info)
{
    bool consumed = false;

    InputEventAckState filter_ack = client_->FilterInputEvent(input_event, latency_info);
    switch (filter_ack) {
    case INPUT_EVENT_ACK_STATE_CONSUMED:
    case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS:
        // Send the ACK and early exit.
        ProcessInputEventAck(
            input_event.type(), filter_ack, latency_info,
            WebInputEventTraits::GetUniqueTouchEventId(input_event), CLIENT);
        // WARNING: |this| may be deleted at this point.
        consumed = true;
        break;
    case INPUT_EVENT_ACK_STATE_UNKNOWN:
        // Simply drop the event.
        consumed = true;
        break;
    default:
        break;
    }

    return consumed;
}

bool InputRouterImpl::OfferToRenderer(const WebInputEvent& input_event,
    const ui::LatencyInfo& latency_info,
    InputEventDispatchType dispatch_type)
{
    DCHECK(input_event.type() != blink::WebInputEvent::GestureFlingStart || static_cast<const blink::WebGestureEvent&>(input_event).data.flingStart.velocityX != 0.0 || static_cast<const blink::WebGestureEvent&>(input_event).data.flingStart.velocityY != 0.0);

    // This conversion is temporary. WebInputEvent should be generated
    // directly from ui::Event with the viewport coordinates. See
    // crbug.com/563730.
    std::unique_ptr<blink::WebInputEvent> event_in_viewport = ui::ScaleWebInputEvent(input_event, device_scale_factor_);
    const WebInputEvent* event_to_send = event_in_viewport ? event_in_viewport.get() : &input_event;

    if (Send(new InputMsg_HandleInputEvent(routing_id(), event_to_send,
            latency_info, dispatch_type))) {
        // Ack messages for ignored ack event types should never be sent by the
        // renderer. Consequently, such event types should not affect event time
        // or in-flight event count metrics.
        if (dispatch_type == InputEventDispatchType::DISPATCH_TYPE_BLOCKING)
            client_->IncrementInFlightEventCount(input_event.type());
        return true;
    }
    return false;
}

void InputRouterImpl::OnInputEventAck(const InputEventAck& ack)
{
    client_->DecrementInFlightEventCount(ack.source);

    if (ack.overscroll) {
        DCHECK(ack.type == WebInputEvent::MouseWheel || ack.type == WebInputEvent::GestureScrollUpdate);
        OnDidOverscroll(*ack.overscroll);
    }

    ProcessInputEventAck(ack.type, ack.state, ack.latency,
        ack.unique_touch_event_id, RENDERER);
}

void InputRouterImpl::OnDidOverscroll(const ui::DidOverscrollParams& params)
{
    client_->DidOverscroll(params);
}

void InputRouterImpl::OnMsgMoveCaretAck()
{
    move_caret_pending_ = false;
    if (next_move_caret_)
        SendMoveCaret(std::move(next_move_caret_));
}

void InputRouterImpl::OnSelectMessageAck()
{
    select_message_pending_ = false;
    if (!pending_select_messages_.empty()) {
        std::unique_ptr<IPC::Message> next_message = std::move(pending_select_messages_.front());
        pending_select_messages_.pop_front();

        SendSelectMessage(std::move(next_message));
    }
}

void InputRouterImpl::OnHasTouchEventHandlers(bool has_handlers)
{
    TRACE_EVENT1("input", "InputRouterImpl::OnHasTouchEventHandlers",
        "has_handlers", has_handlers);

    // Lack of a touch handler indicates that the page either has no touch-action
    // modifiers or that all its touch-action modifiers are auto. Resetting the
    // touch-action here allows forwarding of subsequent gestures even if the
    // underlying touches never reach the router.
    if (!has_handlers)
        touch_action_filter_.ResetTouchAction();

    touch_event_queue_.OnHasTouchEventHandlers(has_handlers);
    client_->OnHasTouchEventHandlers(has_handlers);
}

void InputRouterImpl::OnSetTouchAction(TouchAction touch_action)
{
    // Synthetic touchstart events should get filtered out in RenderWidget.
    DCHECK(touch_event_queue_.IsPendingAckTouchStart());
    TRACE_EVENT1("input", "InputRouterImpl::OnSetTouchAction",
        "action", touch_action);

    touch_action_filter_.OnSetTouchAction(touch_action);

    // TOUCH_ACTION_NONE should disable the touch ack timeout.
    UpdateTouchAckTimeoutEnabled();
}

void InputRouterImpl::OnDidStopFlinging()
{
    DCHECK_GT(active_renderer_fling_count_, 0);
    // Note that we're only guaranteed to get a fling end notification from the
    // renderer, not from any other consumers. Consequently, the GestureEventQueue
    // cannot use this bookkeeping for logic like tap suppression.
    --active_renderer_fling_count_;
    SignalFlushedIfNecessary();

    client_->DidStopFlinging();
}

void InputRouterImpl::ProcessInputEventAck(WebInputEvent::Type event_type,
    InputEventAckState ack_result,
    const ui::LatencyInfo& latency_info,
    uint32_t unique_touch_event_id,
    AckSource ack_source)
{
    TRACE_EVENT2("input", "InputRouterImpl::ProcessInputEventAck", "type",
        WebInputEvent::GetName(event_type), "ack",
        GetEventAckName(ack_result));

    // Note: The keyboard ack must be treated carefully, as it may result in
    // synchronous destruction of |this|. Handling immediately guards against
    // future references to |this|, as with |auto_reset_current_ack_source| below.
    if (WebInputEvent::isKeyboardEventType(event_type)) {
        ProcessKeyboardAck(event_type, ack_result, latency_info);
        // WARNING: |this| may be deleted at this point.
        return;
    }

    base::AutoReset<AckSource> auto_reset_current_ack_source(
        &current_ack_source_, ack_source);

    if (WebInputEvent::isMouseEventType(event_type)) {
        ProcessMouseAck(event_type, ack_result, latency_info);
    } else if (event_type == WebInputEvent::MouseWheel) {
        ProcessWheelAck(ack_result, latency_info);
    } else if (WebInputEvent::isTouchEventType(event_type)) {
        ProcessTouchAck(ack_result, latency_info, unique_touch_event_id);
    } else if (WebInputEvent::isGestureEventType(event_type)) {
        ProcessGestureAck(event_type, ack_result, latency_info);
    } else if (event_type != WebInputEvent::Undefined) {
        ack_handler_->OnUnexpectedEventAck(InputAckHandler::BAD_ACK_MESSAGE);
    }

    SignalFlushedIfNecessary();
}

void InputRouterImpl::ProcessKeyboardAck(blink::WebInputEvent::Type type,
    InputEventAckState ack_result,
    const ui::LatencyInfo& latency)
{
    if (key_queue_.empty()) {
        ack_handler_->OnUnexpectedEventAck(InputAckHandler::UNEXPECTED_ACK);
    } else if (key_queue_.front().event.type() != type) {
        // Something must be wrong. Clear the |key_queue_| and char event
        // suppression so that we can resume from the error.
        key_queue_.clear();
        ack_handler_->OnUnexpectedEventAck(InputAckHandler::UNEXPECTED_EVENT_TYPE);
    } else {
        NativeWebKeyboardEventWithLatencyInfo front_item = key_queue_.front();
        front_item.latency.AddNewLatencyFrom(latency);
        key_queue_.pop_front();

        ack_handler_->OnKeyboardEventAck(front_item, ack_result);
        // WARNING: This InputRouterImpl can be deallocated at this point
        // (i.e.  in the case of Ctrl+W, where the call to
        // HandleKeyboardEvent destroys this InputRouterImpl).
        // TODO(jdduke): crbug.com/274029 - Make ack-triggered shutdown async.
    }
}

void InputRouterImpl::ProcessMouseAck(blink::WebInputEvent::Type type,
    InputEventAckState ack_result,
    const ui::LatencyInfo& latency)
{
    if (type != WebInputEvent::MouseMove)
        return;

    if (mouse_move_queue_.empty()) {
        ack_handler_->OnUnexpectedEventAck(InputAckHandler::UNEXPECTED_ACK);
    } else {
        MouseEventWithLatencyInfo front_item = mouse_move_queue_.front();
        front_item.latency.AddNewLatencyFrom(latency);
        mouse_move_queue_.pop_front();
        ack_handler_->OnMouseEventAck(front_item, ack_result);
    }
}

void InputRouterImpl::ProcessWheelAck(InputEventAckState ack_result,
    const ui::LatencyInfo& latency)
{
    wheel_event_queue_.ProcessMouseWheelAck(ack_result, latency);
}

void InputRouterImpl::ProcessGestureAck(WebInputEvent::Type type,
    InputEventAckState ack_result,
    const ui::LatencyInfo& latency)
{
    if (type == blink::WebInputEvent::GestureFlingStart && ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) {
        ++active_renderer_fling_count_;
    }

    // |gesture_event_queue_| will forward to OnGestureEventAck when appropriate.
    gesture_event_queue_.ProcessGestureAck(ack_result, type, latency);
}

void InputRouterImpl::ProcessTouchAck(InputEventAckState ack_result,
    const ui::LatencyInfo& latency,
    uint32_t unique_touch_event_id)
{
    // |touch_event_queue_| will forward to OnTouchEventAck when appropriate.
    touch_event_queue_.ProcessTouchAck(ack_result, latency,
        unique_touch_event_id);
}

void InputRouterImpl::UpdateTouchAckTimeoutEnabled()
{
    // TOUCH_ACTION_NONE will prevent scrolling, in which case the timeout serves
    // little purpose. It's also a strong signal that touch handling is critical
    // to page functionality, so the timeout could do more harm than good.
    const bool touch_ack_timeout_enabled = touch_action_filter_.allowed_touch_action() != TOUCH_ACTION_NONE;
    touch_event_queue_.SetAckTimeoutEnabled(touch_ack_timeout_enabled);
}

void InputRouterImpl::SignalFlushedIfNecessary()
{
    if (!flush_requested_)
        return;

    if (HasPendingEvents())
        return;

    flush_requested_ = false;
    client_->DidFlush();
}

void InputRouterImpl::SetFrameTreeNodeId(int frameTreeNodeId)
{
    frame_tree_node_id_ = frameTreeNodeId;
}

} // namespace content
